package com.cevent.common.config;


import com.cevent.common.util.UuidUtil;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;

import org.aspectj.lang.Signature;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;




import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;

/**
 * @author cevent
 * @create 2025/1/11 21:45
 */
@Aspect
@Component
public class AOPConfig {
    public static final Logger LOG = LoggerFactory.getLogger(AOPConfig.class);

    // 切点，被aspect切换作用
    @Pointcut("execution(public * com.cevent.*.controller..*Controller.*(..))")
    public void controllerPointCut() {

    }

    // 切点开始之前
    @Before("controllerPointCut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // UUID：每条日志对应唯一流水号，key=UUID与logback变量名相同
        MDC.put("UUID", UuidUtil.getUuid8());
        // 获取servlet请求头信息
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // 接收http请求所有参数
        HttpServletRequest request = requestAttributes.getRequest();
        // 获取签名信息
        Signature signature = joinPoint.getSignature();
        // 从签名获取具体的请求路径
        String reqPath = signature.getName();
        /**
         * 查询：get/query
         * 分页：pageData
         * 详情：detail
         * 保存（修改和新增）：save
         * 删除：delete
         * 文件上传：upload
         * 文件下载：download
         * 其他：
         */
        String opt = "";
        if (reqPath.contains("get") || reqPath.contains("query")) {
            opt = "查询";
        } else if (reqPath.contains("pageData")) {
            opt = "分页";
        } else if (reqPath.contains("detail")) {
            opt = "详情";
        } else if (reqPath.contains("save")) {
            opt = "保存";
        } else if (reqPath.contains("delete")) {
            opt = "删除";
        } else if (reqPath.contains("upload")) {
            opt = "上传";
        } else if (reqPath.contains("download")) {
            opt = "下载";
        } else {
            opt = "自定义";
        }

        // 反射：获取业务名称
        Class clazz = signature.getDeclaringType();
        Field field;
        String serverName = "";
        // 每个controller下，都有一个public static final String SERVER_NAME="XXX" 指定业务行为表
        try {
            field = clazz.getField("SERVER_NAME");
            if (!StringUtils.isEmpty(field)) {
                serverName = (String) field.get(clazz);
            }
        } catch (SecurityException e) {
            LOG.error("尚未获取到业务名称-SecurityException：", e);
            // throw new RuntimeException(e);
        } catch (NoSuchFieldException e) {
            LOG.error("尚未获取到业务名称-NoSuchFieldException：", e);
            // throw new RuntimeException(e);
        } catch (IllegalArgumentException e) {
            LOG.error("尚未获取到业务名称-IllegalArgumentExceptionn：", e);
            // throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            LOG.error("尚未获取到业务名称-IllegalAccessException：", e);
            // throw new RuntimeException(e);
        }

        //------start-----打印日志
        LOG.info("---SERVER【{}】 {} 开始 ---", serverName, opt);
        LOG.info("请求地址：{} ，操作：{}", request.getRequestURL().toString(), request.getMethod());
        LOG.info("请求域名：{} ，token：{}", request.getHeader("domain"), request.getHeader("token"));
        LOG.info("类名：{} 方法：{}", signature.getDeclaringType(), reqPath);
        //TODO 关于ip地址，后期更新为网络ip地址，获取访问区域
        LOG.info("远程地址：{},远程HOST：{}", request.getRemoteAddr(), request.getRemoteHost());

        //TODO 通过token获取redis当前登录人信息

        // 打印,去除不必要的类型，日志缩减
        Object[] objects = joinPoint.getArgs();
        Object[] objs = new Object[objects.length];
        for (int i = 0, l = objects.length; i < l; i++) {
            // 去除的请求参数类型request、response、multipartFile文件类型
            if (objects[i] instanceof ServletRequest || objects[i] instanceof ServletResponse || objects[i] instanceof MultipartFile) {
                continue;
            }
            objs[i] = objects[i];
        }

        // 排除字段，敏感字段(身份证/手机号等)或太长的字段(图片会转为base64长文本)不显示，定义碎片拦截shard
        String[] excludes = {"shard", "phone", "password", "idCard"};
        // 引入AspectJ 阿里图片转换fastjson，进行图片拦截，
        PropertyPreFilters preFilters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter excludeFilter = preFilters.addFilter();
        excludeFilter.addExcludes(excludes);
        LOG.info("exclude filter：不过滤的请求参数：{}", JSONObject.toJSONString(objs, excludeFilter));
    }

    // 环绕通知：打印返回信息，进程切点
    @Around("controllerPointCut()")
    public Object aroundMessage(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = proceedingJoinPoint.proceed();

        // 开始执行时间
        long startTime = System.currentTimeMillis();
        // 排除字段，敏感字段或太长的字段不显示，前端进行datForam参数转换param时，将file转为base64，进行shard:传输，太长需去掉打印
        String[] excludeList = {"shard", "phone", "password", "idCard"};
        PropertyPreFilters filters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter excludes = filters.addFilter();
        excludes.addExcludes(excludeList);
        LOG.info("AOP-Around-返回结果：{}", JSONObject.toJSONString(result, excludes));
        LOG.info("------ 结束耗时 {} ms ------", System.currentTimeMillis() - startTime);
        return result;
    }

}
